<!DOCTYPE html>
<html>
  <head>
    <meta charset='utf-8'>

    <link rel="stylesheet" type="text/css" 
      href="/assets/css/straybirds.css" media="screen" />
    <link rel="stylesheet" type="text/css" 
      href="/assets/css/pygments.css" media="screen" />

    <!-- MathJax Section Start -->

    <script type="text/javascript"
    src="http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML">
    </script>
    <script>
        MathJax.Hub.Config({
              tex2jax: {
              skipTags: ['script', 'noscript', 'style', 'textarea', 'pre']
              }
        });
        MathJax.Hub.Queue(function() {
            var all = MathJax.Hub.getAllJax(), i;
            for(i=0; i < all.length; i += 1) {
                all[i].SourceElement().parentNode.className += ' has-jax';
            }
        });
    </script>

    <!-- MathJax Section End -->

    <!-- Google Analytics Start-->
    <script>
  (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
  (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
  m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
  })(window,document,'script','//www.google-analytics.com/analytics.js','ga');

  ga('create', 'UA-48100787-1', 'minixalpha.github.io');
  ga('send', 'pageview');

</script>

    <!-- Google Analytics End -->

    <title>WSGI 简介</title>
  </head>

  <body>
    <div class="container">
      <header>
        <div class="container">
          <h1>
              <a href="/" title="Home Page"> 潇湘夜雨 </a>
          <span class="github-src">
            <a href ="https://github.com/minixalpha/minixalpha.github.io"
               target="_blank"
               title="Fork me on GitHub">
              <img src="/assets/images/GitHub-Mark-Light-32px.png" alt="">
            </a>
          </span>
          </h1>
        </div>
      </header>

      <aside id="left-side">
        <h2> 分类 </h2>
  <ul class="category-list">
      
            
                <li>
                <a href="/categories/源代码阅读"> 源代码阅读 (20) </a>
                </li>
            
      
            
                <li>
                <a href="/categories/设计模式"> 设计模式 (3) </a>
                </li>
            
      
            
                <li>
                <a href="/categories/科研"> 科研 (6) </a>
                </li>
            
      
            
                <li>
                <a href="/categories/思想"> 思想 (2) </a>
                </li>
            
      
            
                <li style="background-color: #444">
                <a href="/categories/技术"> 技术 (18) </a>
                </li>
            
      
            
                <li>
                <a href="/categories/工具"> 工具 (4) </a>
                </li>
            
      
            
                <li>
                <a href="/categories/虚拟机"> 虚拟机 (1) </a>
                </li>
            
      
            
                <li>
                <a href="/categories/java"> java (11) </a>
                </li>
            
      
            
                <li>
                <a href="/categories/c语言"> c语言 (4) </a>
                </li>
            
      
            
                <li>
                <a href="/categories/读书"> 读书 (1) </a>
                </li>
            
      
            
                <li>
                <a href="/categories/操作系统"> 操作系统 (1) </a>
                </li>
            
      
            
                <li>
                <a href="/categories/英语"> 英语 (10) </a>
                </li>
            
      
            
                <li>
                <a href="/categories/计算机系统"> 计算机系统 (3) </a>
                </li>
            
      
            
                <li>
                <a href="/categories/生活"> 生活 (1) </a>
                </li>
            
      
  </ul>

      </aside>

      <aside id="right-side">
        <h2> 归档 </h2>
  <ul class="archive-list">
    
    
    
        
        
        
        
            
            <li>
                <a href="/2014/08">
                    2014-08 (1)
                </a>
            </li>

        
        
    
        
        
        
        
    
        
        
        
        
    
        
        
        
        
    
        
        
        
        
    
        
        
        
        
            
            <li>
                <a href="/2014/07">
                    2014-07 (5)
                </a>
            </li>

        
        
    
        
        
        
        
    
        
        
        
        
    
        
        
        
        
    
        
        
        
        
    
        
        
        
        
    
        
        
        
        
    
        
        
        
        
    
        
        
        
        
    
        
        
        
        
    
        
        
        
        
    
        
        
        
        
    
        
        
        
        
            
            <li>
                <a href="/2014/05">
                    2014-05 (12)
                </a>
            </li>

        
        
    
        
        
        
        
    
        
        
        
        
    
        
        
        
        
            
            <li>
                <a href="/2014/04">
                    2014-04 (3)
                </a>
            </li>

        
        
    
        
        
        
        
    
        
        
        
        
    
        
        
        
        
    
        
        
        
        
    
        
        
        
        
    
        
        
        
        
    
        
        
        
        
    
        
        
        
        
    
        
        
        
        
    
        
        
        
        
    
        
        
        
        
            
            <li>
                <a href="/2014/03">
                    2014-03 (11)
                </a>
            </li>

        
        
    
        
        
        
        
    
        
        
        
        
    
        
        
        
        
    
        
        
        
        
    
        
        
        
        
    
        
        
        
        
            
            <li>
                <a href="/2014/02">
                    2014-02 (6)
                </a>
            </li>

        
        
    
        
        
        
        
    
        
        
        
        
    
        
        
        
        
            
            <li>
                <a href="/2014/01">
                    2014-01 (3)
                </a>
            </li>

        
        
    
        
        
        
        
    
        
        
        
        
    
        
        
        
        
    
        
        
        
        
    
        
        
        
        
    
        
        
        
        
    
        
        
        
        
    
        
        
        
        
    
        
        
        
        
    
        
        
        
        
            
            <li>
                <a href="/2013/11">
                    2013-11 (10)
                </a>
            </li>

        
        
    
        
        
        
        
    
        
        
        
        
    
        
        
        
        
            
            <li>
                <a href="/2013/10">
                    2013-10 (3)
                </a>
            </li>

        
        
    
        
        
        
        
            
            <li>
                <a href="/2010/09">
                    2010-09 (1)
                </a>
            </li>

        
        
    
  </ul>

      </aside>

      <article>

<h1>WSGI 简介</h1>

<h2>背景</h2>

<p>Python Web 开发中，服务端程序可以分为两个部分，一是服务器程序，二是应用程序。前者负责把客户端请求接收，整理，后者负责具体的逻辑处理。为了方便应用程序的开发，我们把常用的功能封装起来，成为各种Web开发框架，例如 Django, Flask, Tornado。不同的框架有不同的开发方式，但是无论如何，开发出的应用程序都要和服务器程序配合，才能为用户提供服务。这样，服务器程序就需要为不同的框架提供不同的支持。这样混乱的局面无论对于服务器还是框架，都是不好的。对服务器来说，需要支持各种不同框架，对框架来说，只有支持它的服务器才能被开发出的应用使用。</p>

<p>这时候，标准化就变得尤为重要。我们可以设立一个标准，只要服务器程序支持这个标准，框架也支持这个标准，那么他们就可以配合使用。一旦标准确定，双方各自实现。这样，服务器可以支持更多支持标准的框架，框架也可以使用更多支持标准的服务器。</p>

<p>Python Web开发中，这个标准就是 <em>The Web Server Gateway Interface</em>, 即 <strong>WSGI</strong>. 这个标准在<a href="http://www.python.org/dev/peps/pep-0333/">PEP 333</a>中描述，后来，为了支持 Python 3.x, 并且修正一些问题，新的版本在<a href="http://www.python.org/dev/peps/pep-3333/">PEP 3333</a>中描述。</p>

<h2>WSGI 是什么</h2>

<p>WSGI 是服务器程序与应用程序的一个约定，它规定了双方各自需要实现什么接口，提供什么功能，以便二者能够配合使用。</p>

<p>WSGI 不能规定的太复杂，否则对已有的服务器来说，实现起来会困难，不利于WSGI的普及。同时WSGI也不能规定的太多，例如cookie处理就没有在WSGI中规定，这是为了给框架最大的灵活性。要知道WSGI最终的目的是为了方便服务器与应用程序配合使用，而不是成为一个Web框架的标准。</p>

<p>另一方面，WSGI需要使得middleware（是中间件么？）易于实现。middleware处于服务器程序与应用程序之间，对服务器程序来说，它相当于应用程序，对应用程序来说，它相当于服务器程序。这样，对用户请求的处理，可以变成多个 middleware 叠加在一起，每个middleware实现不同的功能。请求从服务器来的时候，依次通过middleware，响应从应用程序返回的时候，反向通过层层middleware。我们可以方便地添加，替换middleware，以便对用户请求作出不同的处理。</p>

<h2>WSGI 内容概要</h2>

<p>WSGI主要是对应用程序与服务器端的一些规定，所以，它的主要内容就分为两个部分。</p>

<h2>应用程序</h2>

<p>WSGI规定：</p>
<div class="highlight"><pre><code class="text language-text" data-lang="text">1. 应用程序需要是一个可调用的对象
</code></pre></div>
<p>在Python中:</p>

<ul>
<li>可以是函数</li>
<li>可以是一个实例，它的类实现了<code>__call__</code>方法</li>
<li>可以是一个类，这时候，用这个类生成实例的过程就相当于调用这个类</li>
</ul>

<p>同时，WSGI规定：</p>
<div class="highlight"><pre><code class="text language-text" data-lang="text">2. 可调用对象接收两个参数
</code></pre></div>
<p>这样，如果这个对象是函数的话，它看起来要是这个样子：</p>
<div class="highlight"><pre><code class="python language-python" data-lang="python"><span class="c"># callable function</span>
<span class="k">def</span> <span class="nf">application</span><span class="p">(</span><span class="n">environ</span><span class="p">,</span> <span class="n">start_response</span><span class="p">):</span>
    <span class="k">pass</span>
</code></pre></div>
<p>如果这个对象是一个类的话，它看起来是这个样子：</p>
<div class="highlight"><pre><code class="python language-python" data-lang="python"><span class="c"># callable class</span>
<span class="k">class</span> <span class="nc">Application</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">environ</span><span class="p">,</span> <span class="n">start_response</span><span class="p">):</span>
        <span class="k">pass</span>
</code></pre></div>
<p>如果这个对象是一个类的实例，那么，这个类看起来是这个样子：</p>
<div class="highlight"><pre><code class="python language-python" data-lang="python"><span class="c"># callable object</span>
<span class="k">class</span> <span class="nc">ApplicationObj</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">__call__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">environ</span><span class="p">,</span> <span class="n">start_response</span><span class="p">):</span>
        <span class="k">pass</span>
</code></pre></div>
<p>最后，WSGI还规定:</p>
<div class="highlight"><pre><code class="text language-text" data-lang="text">3.可调用对象要返回一个值，这个值是可迭代的。
</code></pre></div>
<p>这样的话，前面的三个例子就变成：</p>
<div class="highlight"><pre><code class="python language-python" data-lang="python"><span class="n">HELLO_WORLD</span> <span class="o">=</span> <span class="n">b</span><span class="s">&quot;Hello world!</span><span class="se">\n</span><span class="s">&quot;</span>


<span class="c"># callable function</span>
<span class="k">def</span> <span class="nf">application</span><span class="p">(</span><span class="n">environ</span><span class="p">,</span> <span class="n">start_response</span><span class="p">):</span>
    <span class="k">return</span> <span class="p">[</span><span class="n">HELLO_WORLD</span><span class="p">]</span>


<span class="c"># callable class</span>
<span class="k">class</span> <span class="nc">Application</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">environ</span><span class="p">,</span> <span class="n">start_response</span><span class="p">):</span>
        <span class="k">pass</span>

    <span class="k">def</span> <span class="nf">__iter__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="k">yield</span> <span class="n">HELLO_WORLD</span>


<span class="c"># callable object</span>
<span class="k">class</span> <span class="nc">ApplicationObj</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">__call__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">environ</span><span class="p">,</span> <span class="n">start_response</span><span class="p">):</span>
        <span class="k">return</span> <span class="p">[</span><span class="n">HELLO_WORLD</span><span class="p">]</span>
</code></pre></div>
<p>你可能会说，不是啊，我们平时写的web程序不是这样啊。
比如如果使用web.py框架的话，一个典型的应用可能是这样的:</p>
<div class="highlight"><pre><code class="python language-python" data-lang="python"><span class="k">class</span> <span class="nc">hello</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">GET</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="k">return</span> <span class="s">&#39;Hello, world!&#39;</span>
</code></pre></div>
<p>这是由于框架已经把WSGI中规定的一些东西封装起来了，我们平时用框架时，看不到这些东西，只需要直接实现我们的逻辑，再返回一个值就好了。其它的东西框架帮我们做好了。这也是框架的价值所在，把常用的东西封装起来，让使用者只需要关注最重要的东西。</p>

<p>当然，<strong>WSGI关于应用程序的规定不只这些</strong>，但是现在，我们只需要知道这些就足够了。下面，再介绍服务器程序。</p>

<h2>服务器程序</h2>

<p>服务器程序会在每次客户端的请求传来时，调用我们写好的应用程序，并将处理好的结果返回给客户端。</p>

<p>WSGI规定：</p>
<div class="highlight"><pre><code class="text language-text" data-lang="text">4.服务器程序需要调用应用程序
</code></pre></div>
<p>服务器程序看起来大概是这个样子的：</p>
<div class="highlight"><pre><code class="python language-python" data-lang="python"><span class="k">def</span> <span class="nf">run</span><span class="p">(</span><span class="n">application</span><span class="p">):</span>
    <span class="n">environ</span> <span class="o">=</span> <span class="p">{}</span>

    <span class="k">def</span> <span class="nf">start_response</span><span class="p">(</span><span class="n">status</span><span class="p">,</span> <span class="n">response_headers</span><span class="p">,</span> <span class="n">exc_info</span><span class="o">=</span><span class="bp">None</span><span class="p">):</span>
        <span class="k">pass</span>

    <span class="n">result</span> <span class="o">=</span> <span class="n">application</span><span class="p">(</span><span class="n">environ</span><span class="p">,</span> <span class="n">start_response</span><span class="p">)</span>

    <span class="k">def</span> <span class="nf">write</span><span class="p">(</span><span class="n">data</span><span class="p">):</span>
        <span class="k">pass</span>

    <span class="k">for</span> <span class="n">data</span> <span class="ow">in</span> <span class="n">result</span><span class="p">:</span>
        <span class="n">write</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
</code></pre></div>
<p>这里可以看出服务器程序是如何与应用程序配合完成用户请求的。</p>

<p>WSGI规定了应用程序需要一个可调用对象，有两个参数，返回一个可迭代对象。在服务器
程序中，针对这几个规定，做了以下几件事：</p>

<ul>
<li>把应用程序需要的两个参数设置好</li>
<li>调用应用程序</li>
<li>迭代访问应用程序的返回结果，并将其传回客户端</li>
</ul>

<p>你可以从中发现，应用程序需要的两个参数，一个是一个dict对象，一个是函数。它们到底有什么用呢？这都不是我们现在应该关心的，现在只需要知道，服务器程序大概做了什么事情就好了，后面，我们会深入讨论这些细节。</p>

<h2>middleware</h2>

<p>另外，有些功能可能介于服务器程序和应用程序之间，例如，服务器拿到了客户端请求的URL,
不同的URL需要交由不同的函数处理，这个功能叫做 URL Routing，这个功能就可以放在二者中间实现，这个中间层就是 middleware。</p>

<p>middleware对服务器程序和应用是透明的，也就是说，服务器程序以为它就是应用程序，而应用程序以为它就是服务器。这就告诉我们，middleware需要把自己伪装成一个服务器，接受应用程序，调用它，同时middleware还需要把自己伪装成一个应用程序，传给服务器程序。</p>

<p>其实无论是服务器程序，middleware 还是应用程序，都在服务端，为客户端提供服务，之所以把他们抽象成不同层，就是为了控制复杂度，使得每一次都不太复杂，各司其职。</p>

<p>下面，我们看看middleware大概是什么样子的。</p>
<div class="highlight"><pre><code class="python language-python" data-lang="python"><span class="c"># URL Routing middleware</span>
<span class="k">def</span> <span class="nf">urlrouting</span><span class="p">(</span><span class="n">url_app_mapping</span><span class="p">):</span>
    <span class="k">def</span> <span class="nf">midware_app</span><span class="p">(</span><span class="n">environ</span><span class="p">,</span> <span class="n">start_response</span><span class="p">):</span>
        <span class="n">url</span> <span class="o">=</span> <span class="n">environ</span><span class="p">[</span><span class="s">&#39;PATH_INFO&#39;</span><span class="p">]</span>
        <span class="n">app</span> <span class="o">=</span> <span class="n">url_app_mapping</span><span class="p">[</span><span class="n">url</span><span class="p">]</span>

        <span class="n">result</span> <span class="o">=</span> <span class="n">app</span><span class="p">(</span><span class="n">environ</span><span class="p">,</span> <span class="n">start_response</span><span class="p">)</span>

        <span class="k">return</span> <span class="n">result</span>

    <span class="k">return</span> <span class="n">midware_app</span>
</code></pre></div>
<p>函数 <code>midware_app</code>就是一个简单的middleware：对服务器而言，它是一个应用程序，是一个可调用对象， 有两个参数，返回一个可调用对象。对应用程序而言，它是一个服务器，为应用程序提供了参数，并且调用了应用程序。</p>

<p>另外，这里的<code>urlrouting</code>函数，相当于一个函数生成器，你给它不同的 url-app 映射关系，它会生成相应的具有 url routing功能的 middleware。</p>

<p>如果你仅仅想简单了解一下WSGI是什么，相信到这里，你差不多明白了，下面会介绍WSGI的细节，这些细节来自 <a href="http://www.python.org/dev/peps/pep-3333/">PEP3333</a>， 如果没有兴趣，到这里 <strong>可以停止</strong>了。</p>

<h2>WSGI详解</h2>

<p>注意：以 点 开始的解释是WSGI规定 <strong>必须满足</strong> 的。</p>

<h3>应用程序</h3>

<ul>
<li>应用程序是可调用对象</li>
<li>可调用对象有两个位置参数<br>
所谓位置参数就是调用的时候，依靠位置来确定参数的语义，而不是参数名，也就是说服务
器调用应用程序时，应该是这样：</li>
</ul>
<div class="highlight"><pre><code class="python language-python" data-lang="python"><span class="n">application</span><span class="p">(</span><span class="n">env</span><span class="p">,</span> <span class="n">start_response</span><span class="p">)</span>
</code></pre></div>
<p>而不是这样：</p>
<div class="highlight"><pre><code class="python language-python" data-lang="python"><span class="n">application</span><span class="p">(</span><span class="n">start_response</span><span class="o">=</span><span class="n">start_response</span><span class="p">,</span> <span class="n">environ</span><span class="o">=</span><span class="n">env</span><span class="p">)</span>
</code></pre></div>
<p>所以，参数名其实是可以随便起的，只不过为了表义清楚，我们起了<code>environ</code> 和 <code>start_response</code>。</p>

<ul>
<li>第一个参数environ是Python内置的dict对象，应用程序可以对这个参数任意修改。</li>
<li>environ参数必须包含 WSGI 需要的一些变量(详见后文)<br>
也可以包含一些扩展参数，命名规范见后文</li>
<li>start_response参数是一个可调用对象。接受两个位置参数，一个可选参数。
例如：
<code>python
start_response(status, response_headers, exc_info=None)
</code>
status参数是状态码，例如 <code>200 OK</code> 。 </li>
</ul>

<p>response<em>headers参数是一个列表，列表项的形式为(header</em>name, header_value)。  </p>

<p>exc_info参数在错误处理的时候使用。</p>

<p>status和response_headers的具体内容可以参考 <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html#sec6">HTTP 协议 Response部分</a>。</p>

<ul>
<li>start_response必须返回一个可调用对象： <code>write(body_data)</code></li>
<li>应用程序必须返回一个可迭代对象。</li>
<li>应用程序不应假设返回的可迭代对象被遍历至终止，因为遍历过程可能出现错误。</li>
<li>应用程序必须在第一次返回可迭代数据之前调用 start<em>response 方法。<br>
这是因为可迭代数据是 返回数据的 <code>body</code> 部分，在它返回之前，需要使用 <code>start_response</code>
返回 response</em>headers 数据。</li>
</ul>

<h3>服务器程序</h3>

<ul>
<li>服务器必须将可迭代对象的内容传递给客户端，可迭代对象会产生bytestrings，必须完全完成每个bytestring后才能请求下一个。</li>
<li>假设result 为应用程序的返回的可迭代对象。如果len(result) 调用成功，那么result必须是可累积的。</li>
<li>如果result有<code>close</code>方法，那么每次完成对请求的处理时，必须调用它，无论这次请求正常完成，还是遇到了错误。</li>
<li>服务器程序禁止使用可迭代对象的其它属性，除非这个可迭代对象是一个特殊类的实例，这个类会被 <code>wsgi.file_wrapper</code> 定义。</li>
</ul>

<p>根据上述内容，我们的服务器程序看起来会是这个样子：</p>
<div class="highlight"><pre><code class="python language-python" data-lang="python"><span class="k">def</span> <span class="nf">run</span><span class="p">(</span><span class="n">application</span><span class="p">):</span>
    <span class="n">environ</span> <span class="o">=</span> <span class="p">{}</span>

    <span class="c"># set environ</span>
    <span class="k">def</span> <span class="nf">write</span><span class="p">(</span><span class="n">data</span><span class="p">):</span>
        <span class="k">pass</span>

    <span class="k">def</span> <span class="nf">start_response</span><span class="p">(</span><span class="n">status</span><span class="p">,</span> <span class="n">response_headers</span><span class="p">,</span> <span class="n">exc_info</span><span class="o">=</span><span class="bp">None</span><span class="p">):</span>
        <span class="k">return</span> <span class="n">write</span>

    <span class="k">try</span><span class="p">:</span>
        <span class="n">result</span> <span class="o">=</span> <span class="n">application</span><span class="p">(</span><span class="n">environ</span><span class="p">,</span> <span class="n">start_response</span><span class="p">)</span>
    <span class="k">finally</span><span class="p">:</span>
        <span class="k">if</span> <span class="nb">hasattr</span><span class="p">(</span><span class="n">result</span><span class="p">,</span> <span class="s">&#39;close&#39;</span><span class="p">):</span>
            <span class="n">result</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>

    <span class="k">if</span> <span class="nb">hasattr</span><span class="p">(</span><span class="n">result</span><span class="p">,</span> <span class="s">&#39;__len__&#39;</span><span class="p">):</span>
        <span class="c"># result must be accumulated</span>
        <span class="k">pass</span>


    <span class="k">for</span> <span class="n">data</span> <span class="ow">in</span> <span class="n">result</span><span class="p">:</span>
        <span class="n">write</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
</code></pre></div>
<p>应用程序看起来是这个样子：</p>
<div class="highlight"><pre><code class="python language-python" data-lang="python"><span class="n">HELLO_WORLD</span> <span class="o">=</span> <span class="n">b</span><span class="s">&quot;Hello world!</span><span class="se">\n</span><span class="s">&quot;</span>


<span class="c"># callable function</span>
<span class="k">def</span> <span class="nf">application</span><span class="p">(</span><span class="n">environ</span><span class="p">,</span> <span class="n">start_response</span><span class="p">):</span>
    <span class="n">status</span> <span class="o">=</span> <span class="s">&#39;200 OK&#39;</span>
    <span class="n">response_headers</span> <span class="o">=</span> <span class="p">[(</span><span class="s">&#39;Content-type&#39;</span><span class="p">,</span> <span class="s">&#39;text/plain&#39;</span><span class="p">)]</span>
    <span class="n">start_response</span><span class="p">(</span><span class="n">status</span><span class="p">,</span> <span class="n">response_headers</span><span class="p">)</span>

    <span class="k">return</span> <span class="p">[</span><span class="n">HELLO_WORLD</span><span class="p">]</span>
</code></pre></div>
<p>下面我们再详细介绍之前提到的一些数据结构</p>

<h3>environ 变量</h3>

<p>environ 变量需要包含 CGI 环境变量，它们在<a href="http://tools.ietf.org/html/draft-robinson-www-interface-00">The Common Gateway Interface Specification</a> 中定义，下面列出的变量<strong>必须</strong>包含在 enciron变量中：</p>

<ul>
<li>REQUEST_METHOD<br>
HTTP 请求方法，例如 &quot;GET&quot;, &quot;POST&quot;</li>
<li>SCRIPT_NAME<br>
URL 路径的起始部分对应的应用程序对象，如果应用程序对象对应服务器的根，那么这个值可以为空字符串</li>
<li>PATH_INFO<br>
URL 路径除了起始部分后的剩余部分，用于找到相应的应用程序对象，如果请求的路径就是根路径，这个值为空字符串</li>
<li>QUERY_STRING<br>
URL路径中 <code>?</code> 后面的部分</li>
<li>CONTENT_TYPE<br>
HTTP 请求中的 <code>Content-Type</code> 部分</li>
<li>CONTENT_LENGTH<br>
HTTP 请求中的<code>Content-Lengh</code> 部分</li>
<li>SERVER<em>NAME, SERVER</em>PORT<br>
与 SCRIPT<em>NAME，PATH</em>INFO 共同构成完整的  URL，它们永远不会为空。但是，如果 HTTP<em>HOST 存在的话，当构建 URL 时， HTTP</em>HOST优先于SERVER_NAME。</li>
<li>SERVER<em>PROTOCOL<br>
客户端使用的协议，例如 &quot;HTTP/1.0&quot;, &quot;HTTP/1.1&quot;, 它决定了如何处理 HTTP 请求的头部。这个名字其实应该叫 <code>REQUEST_PROTOCOL</code>，因为它表示的是客户端请求的协议，而不是服务端响应的协议。但是为了和CGI兼容，我们只好叫这个名字了。
*HTTP</em> Variables<br>
这个是一个系列的变量名，都以<code>HTTP</code>开头，对应客户端支持的HTTP请求的头部信息。</li>
</ul>

<p>WSGI 有一个参考实现，叫 wsgiref，里面有一个示例，我们这里引用这个示例的结果，展现一下这些变量，以便有一个直观的体会，这个示例访问的 URL 为 <code>http://localhost:8000/xyz?abc</code></p>

<p>上面提到的变量值为：</p>
<div class="highlight"><pre><code class="python language-python" data-lang="python"><span class="n">REQUEST_METHOD</span> <span class="o">=</span> <span class="s">&#39;GET&#39;</span>
<span class="n">SCRIPT_NAME</span> <span class="o">=</span> <span class="s">&#39;&#39;</span>
<span class="n">PATH_INFO</span> <span class="o">=</span> <span class="s">&#39;/xyz&#39;</span>
<span class="n">QUERY_STRING</span> <span class="o">=</span> <span class="s">&#39;abc&#39;</span>
<span class="n">CONTENT_TYPE</span> <span class="o">=</span> <span class="s">&#39;text/plain&#39;</span>
<span class="n">CONTENT_LENGTH</span> <span class="o">=</span> <span class="s">&#39;&#39;</span>
<span class="n">SERVER_NAME</span> <span class="o">=</span> <span class="s">&#39;minix-ubuntu-desktop&#39;</span>
<span class="n">SERVER_PORT</span> <span class="o">=</span> <span class="s">&#39;8000&#39;</span>
<span class="n">SERVER_PROTOCOL</span> <span class="o">=</span> <span class="s">&#39;HTTP/1.1&#39;</span>

<span class="n">HTTP_ACCEPT</span> <span class="o">=</span> <span class="s">&#39;text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8&#39;</span>
<span class="n">HTTP_ACCEPT_ENCODING</span> <span class="o">=</span> <span class="s">&#39;gzip,deflate,sdch&#39;</span>
<span class="n">HTTP_ACCEPT_LANGUAGE</span> <span class="o">=</span> <span class="s">&#39;en-US,en;q=0.8,zh;q=0.6,zh-CN;q=0.4,zh-TW;q=0.2&#39;</span>
<span class="n">HTTP_CONNECTION</span> <span class="o">=</span> <span class="s">&#39;keep-alive&#39;</span>
<span class="n">HTTP_HOST</span> <span class="o">=</span> <span class="s">&#39;localhost:8000&#39;</span>
<span class="n">HTTP_USER_AGENT</span> <span class="o">=</span> <span class="s">&#39;Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.77 Safari/537.36&#39;</span>
</code></pre></div>
<p>另外，服务器还应该（非必须）提供尽可能多的CGI变量，如果支持SSL的话，还应该提供<a href="http://www.modssl.org/docs/2.8/ssl_reference.html#ToC25">Apache SSL 环境变量</a>。</p>

<p>服务器程序应该在文档中对它提供的变量进行说明，应用程序应该检查它需要的变量是否存在。</p>

<p>除了 CGI 定义的变量外，服务器程序还可以包含和操作系统相关的环境变量，但这并非必须。</p>

<p>但是，下面列出的这些 WSGI 相关的变量必须要包含：</p>

<ul>
<li>wsgi.version<br>
值的形式为 (1, 0) 表示 WSGI 版本 1.0</li>
<li>wsgi.url_scheme<br>
表示 url 的模式，例如 &quot;https&quot; 还是 &quot;http&quot;</li>
<li>wsgi.input<br>
输入流，HTTP请求的 body  部分可以从这里读取</li>
<li>wsgi.erros<br>
输出流，如果出现错误，可以写往这里</li>
<li>wsgi.multithread<br>
如果应用程序对象可以被同一进程中的另一线程同时调用，这个值为True</li>
<li>wsgi.multiprocess<br>
如果应用程序对象可以同时被另一个进程调用，这个值为True</li>
<li>wsgi.run_once<br>
如果服务器希望应用程序对象在包含它的进程中只被调用一次，那么这个值为True</li>
</ul>

<p>这些值在 wsgiref示例中的值为：</p>
<div class="highlight"><pre><code class="python language-python" data-lang="python"><span class="n">wsgi</span><span class="o">.</span><span class="n">errors</span> <span class="o">=</span> <span class="o">&lt;</span><span class="nb">open</span> <span class="nb">file</span> <span class="s">&#39;&lt;stderr&gt;&#39;</span><span class="p">,</span> <span class="n">mode</span> <span class="s">&#39;w&#39;</span> <span class="n">at</span> <span class="mh">0xb735f0d0</span><span class="o">&gt;</span>
<span class="n">wsgi</span><span class="o">.</span><span class="n">file_wrapper</span> <span class="o">=</span> <span class="o">&lt;</span><span class="k">class</span> <span class="nc">wsgiref</span><span class="o">.</span><span class="n">util</span><span class="o">.</span><span class="n">FileWrapper</span> <span class="n">at</span> <span class="mh">0xb70525fc</span><span class="o">&gt;</span>
<span class="n">wsgi</span><span class="o">.</span><span class="n">input</span> <span class="o">=</span> <span class="o">&lt;</span><span class="n">socket</span><span class="o">.</span><span class="n">_fileobject</span> <span class="nb">object</span> <span class="n">at</span> <span class="mh">0xb7050e6c</span><span class="o">&gt;</span>
<span class="n">wsgi</span><span class="o">.</span><span class="n">multiprocess</span> <span class="o">=</span> <span class="bp">False</span>
<span class="n">wsgi</span><span class="o">.</span><span class="n">multithread</span> <span class="o">=</span> <span class="bp">True</span>
<span class="n">wsgi</span><span class="o">.</span><span class="n">run_once</span> <span class="o">=</span> <span class="bp">False</span>
<span class="n">wsgi</span><span class="o">.</span><span class="n">url_scheme</span> <span class="o">=</span> <span class="s">&#39;http&#39;</span>
<span class="n">wsgi</span><span class="o">.</span><span class="n">version</span> <span class="o">=</span> <span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
</code></pre></div>
<p>另外，environ中还可以包含服务器自己定义的一些变量，这些变量应该只包含<br>
* 小写字母
* 数字
* 点
* 下划线
* 独立的前缀</p>

<p>例如，mod<em>python定义的变量名应该为mod</em>python.var_name的形式。</p>

<h2>输入流及错误流(Input and Error Streams)</h2>

<p>服务器程序提供的输入流及错误流必须包含以下方法：</p>

<ul>
<li>read(size)</li>
<li>readline()</li>
<li>readlines(hint)</li>
<li><strong>iter</strong>()</li>
<li>flush()</li>
<li>write()</li>
<li>writelines(seq)</li>
</ul>

<p>应用程序使用输入流对象及错误流对象时，只能使用这些方法，禁止使用其它方法，特别是，
禁止应用程序关闭这些流。</p>

<h2>start_response()</h2>

<p>start_response是HTTP响应的开始，它的形式为：</p>
<div class="highlight"><pre><code class="python language-python" data-lang="python"><span class="n">start_response</span><span class="p">(</span><span class="n">status</span><span class="p">,</span> <span class="n">response_headers</span><span class="p">,</span> <span class="n">exc_info</span><span class="o">=</span><span class="bp">None</span><span class="p">)</span>
</code></pre></div>
<p>返回一个可调用对象，这个可调用对象形式为：</p>
<div class="highlight"><pre><code class="python language-python" data-lang="python"><span class="n">write</span><span class="p">(</span><span class="n">body_data</span><span class="p">)</span>
</code></pre></div>
<p>status 表示  HTTP 状态码，例如 &quot;200 OK&quot;, &quot;404 Not Found&quot;，它们在 <a href="http://www.faqs.org/rfcs/rfc2616.html">RFC 2616</a>中定义，status禁止包含控制字符。</p>

<p>response<em>headers 是一个列表，列表项是一个二元组： (header</em>name, heaer<em>value) ，
每个 header</em>name 都必须是 <a href="http://www.faqs.org/rfcs/rfc2616.html">RFC 2616</a>  4.2 节中定义的HTTP 头部名。header_value 禁止包含控制字符。</p>

<p>另外，服务器程序必须保证正确的headers 被返回给客户端，如果应用程序没有返回headers，服务器必须添加它。</p>

<p>应用程序和middleware禁止使用 HTTP/1.1 中的 &quot;hop-by-hop&quot;特性，以及其它可能影响客户端与服务器永久连接的特性。</p>

<p>start<em>response 被调用时，服务器应该检查 headers 中的错误，另外，禁止 start</em>response直接将 response<em>headers传递给客户端，它必须把它们存储起来，一直到应用程序第一次迭代返回一个非空数据后，才能将response</em>headers传递给客户端。这其实是在说，HTTP响应body部分必须有数据，不能只返回一个header。</p>

<p>start<em>response的第三个参数是一个可选参数，exc</em>info，它必须和Python的 sys.exc<em>info()返回的数据有相同类型。当处理请求的过程遇到错误时，这个参数会被设置，同时调用 start</em>response。如果提供了exc<em>info，但是HTTP headers 还没有输出，那么 start</em>response需要将当前存储的 HTTP response headers替换成一个新值。但是，如果提供了exc<em>info，同时 HTTP headers已经输出了，那么 start</em>response 必须 raise 一个 error。禁止应用程序处理
start_response raise出的  exceptions，应该交给服务器程序处理。</p>

<p>当且仅当提供 exc<em>info参数时，start</em>response才可以被调用多于一次。换句话说，要是没提供这个参数，start_response在当前应用程序中调用后，禁止再调用。</p>

<p>为了避免循环引用，start<em>response实现时需要保证 exc</em>info在函数调用后不再包含引用。
也就是说start<em>response用完 exc</em>info后，需要保证执行一句</p>
<div class="highlight"><pre><code class="python language-python" data-lang="python"><span class="n">exc_info</span> <span class="o">=</span> <span class="bp">None</span>
</code></pre></div>
<p>这可以通过 try/finally实现。</p>

<h2>处理 Content-Length Header</h2>

<p>如果应用程序支持 Content-Length，那么服务器程序传递的数据大小不应该超过 Content-Length，当发送了足够的数据后，应该停止迭代，或者 raise 一个 error。当然，如果应用程序返回的数据大小没有它指定的Content-Length那么多，那么服务器程序应该关闭连接，使用Log记录，或者报告错误。</p>

<p>如果应用程序不支持Content-Length,那么服务器程序应该选择一种方法处理这种情况。最简单的方法就是当响应完成后，关闭与客户端的连接。</p>

<h3>缓冲与流(Buffering and Streaming)</h3>

<p>一般情况下，应用程序会把需要返回的数据放在缓冲区里，然后一次性发送出去。之前说的应用程序会返回一个可迭代对象，多数情况下，这个可迭代对象，都只有一个元素，这个元素包含了HTML内容。但是在有些情况下，数据太大了，无法一次性在内存中存储这些数据，所以就需要做成一个可迭代对象，每次迭代只发送一块数据。</p>

<p>禁止服务器程序延迟任何一块数据的传送，要么把一块数据完全传递给客户端，要么保证在产生下一块数据时，继续传递这一块数据。</p>

<h2>middleware 处理数据</h2>

<p>如果 middleware调用的应用程序产生了数据，那么middleware至少要产生一个数据，即使它想等数据积累到一定程度再返回，它也需要产生一个空的bytestring。
注意，这也意味着只要middleware调用的应用程序产生了一个可迭代对象，middleware也必须返回一个可迭代对象。
同时，禁止middleware使用可调用对象write传递数据，write是middleware调用的应用程序使用的。</p>

<h3>write 可调用对象</h3>

<p>一些已经存在的应用程序框架使用了write函数或方法传递数据，并且没有使用缓冲区。不幸的是，根据WSGI中的要求，应用程序需要返回可迭代对象，这样就无法实现这些API,为了允许这些API 继续使用，WSGI要求 start_response 返回一个 write 可调用对象，这样应用程序就能使用这个  write  了。</p>

<p>但是，如果能避免使用这个 write，最好避免使用，这是为兼容以前的应用程序而设计的。这个write的参数是HTTP response body的一部分，这意味着在write()返回前，必须保证传给它的数据已经完全被传送了，或者已经放在缓冲区了。</p>

<p>应用程序必须返回一个可迭代对象，即使它使用write产生HTTP response body。</p>

<p>这里可以发现，有两中传递数据的方式，一种是直接使用write传递，一种是应用程序返回可迭代对象后，再将这个可迭代对象传递，如果同时使用这两种方式，前者的数据必须在后者之前传递。</p>

<h3>Unicode</h3>

<p>HTTP 不支持 Unicode, 所有编码/解码都必须由应用程序完成，所有传递给或者来自server的字符串都必须是 <code>str</code> 或者 <code>bytes</code>类型，而不是<code>unicode</code>。</p>

<p>注意传递给start_response的数据，其编码都必须遵循 <a href="http://www.faqs.org/rfcs/rfc2616.html">RFC 2616</a>， 即使用 ISO-8859-1  或者  <a href="http://www.faqs.org/rfcs/rfc2047.html">RFC 2047</a> MIME 编码。</p>

<p>WSGI 中据说的 <code>bytestrings</code> ， 在Python3中指 <code>bytes</code>，在以前的Python版本中，指
<code>str</code>。</p>

<h3>错误处理(Error Handling)</h3>

<p>应用程序应该捕获它们自己的错误，internal erros， 并且将相关错误信息返回给浏览器。
WSGI 提供了一种错误处理的方式，这就是之前提到的 exc_info参数。下面是 PEP 3333中提供的一段示例：</p>
<div class="highlight"><pre><code class="python language-python" data-lang="python"><span class="k">try</span><span class="p">:</span>
    <span class="c"># regular application code here</span>
    <span class="n">status</span> <span class="o">=</span> <span class="s">&quot;200 Froody&quot;</span>
    <span class="n">response_headers</span> <span class="o">=</span> <span class="p">[(</span><span class="s">&quot;content-type&quot;</span><span class="p">,</span> <span class="s">&quot;text/plain&quot;</span><span class="p">)]</span>
    <span class="n">start_response</span><span class="p">(</span><span class="n">status</span><span class="p">,</span> <span class="n">response_headers</span><span class="p">)</span>
    <span class="k">return</span> <span class="p">[</span><span class="s">&quot;normal body goes here&quot;</span><span class="p">]</span>
<span class="k">except</span><span class="p">:</span>
    <span class="c"># XXX should trap runtime issues like MemoryError, KeyboardInterrupt</span>
    <span class="c">#     in a separate handler before this bare &#39;except:&#39;...</span>
    <span class="n">status</span> <span class="o">=</span> <span class="s">&quot;500 Oops&quot;</span>
    <span class="n">response_headers</span> <span class="o">=</span> <span class="p">[(</span><span class="s">&quot;content-type&quot;</span><span class="p">,</span> <span class="s">&quot;text/plain&quot;</span><span class="p">)]</span>
    <span class="n">start_response</span><span class="p">(</span><span class="n">status</span><span class="p">,</span> <span class="n">response_headers</span><span class="p">,</span> <span class="n">sys</span><span class="o">.</span><span class="n">exc_info</span><span class="p">())</span>
    <span class="k">return</span> <span class="p">[</span><span class="s">&quot;error body goes here&quot;</span><span class="p">]</span>
</code></pre></div>
<p>当出现异常时，start<em>response的exc</em>info参数被设置成 sys.exc_info()，这个函数会返回当前的异常。</p>

<h3>HTTP 1.1 Expect/Continue</h3>

<p>如果服务器程序要实现 HTTP 1.1，那么它必须提供对 HTTP 1.1 <code>expect/continue</code>机制的支持。</p>

<h2>其它内容</h2>

<p>在 PEP 3333 中，还包含了其它内容，例如：</p>

<ul>
<li>HTTP 特性</li>
<li>线程支持</li>
<li>实现时需要注意的地方：包括，扩展API，应用程序配置，URL重建等 </li>
</ul>

<p>这里就不作过多介绍了。</p>

<h2>扩展阅读</h2>

<p>这篇文章主要是我阅读 PEP 3333 后的理解和记录，有些地方可能没有理解正确或者没有写全，下面提供一些资源供扩展阅读。</p>

<ul>
<li><a href="http://www.python.org/dev/peps/pep-3333/">PEP 3333</a><br>
不解释</li>
<li><a href="http://wsgi.readthedocs.org/en/latest/">WSGI org</a><br>
看起来好像官方网站的样子，覆盖了关于WSGI的方方面面，包含学习资源，支持WSGI的框架列表，服务器列表，应用程序列表，middleware和库等等。</li>
<li><a href="https://pypi.python.org/pypi/wsgiref">wsgiref</a><br>
WSGI的参考实现，阅读源代码后有利于对WSGI的理解。我在GitHub上有自己阅读后的注释版本，并且作了一些图，有需要可以看这里：<a href="https://github.com/minixalpha/SourceLearning/tree/master/wsgiref-0.1.2">wsgiref 源代码阅读</a></li>
</ul>

<p>另外，还有一些文章介绍了一些基本概念和一些有用的实例，非常不错。</p>

<ul>
<li><a href="http://blog.kenshinx.me/blog/wsgi-research/">Wsgi研究</a></li>
<li><a href="http://linluxiang.iteye.com/blog/799163">wsgi初探</a> </li>
</ul>


      </article>

      <div class="comments">
        
          <div id="disqus_thread"></div>
 <script type="text/javascript">
     /* * * CONFIGURATION VARIABLES: EDIT BEFORE PASTING INTO YOUR WEBPAGE * * */
     var disqus_shortname = 'minixalpha'; // required: replace example with your forum shortname

     /* * * DON'T EDIT BELOW THIS LINE * * */
     (function() {
         var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
         dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js';
         (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
     })();
 </script>
 <noscript>Please enable JavaScript to view the <a href="http://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>
    <a href="http://disqus.com" class="dsq-brlink">comments powered by <span class="logo-disqus">Disqus</span></a>
    

        
      </div>


      <footer>
        Copyright (c) minixalpha 2014
      </footer>

    </div>
  </body>
</html>
